﻿using CommonEngine.STSDB.General.Extensions;
using CommonEngine.STSDB.General.Persist;
using System;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;

namespace CommonEngine.STSDB.Data
{
    public class DataIndexerPersist : IIndexerPersist<IData>
    {
        public readonly Action<BinaryWriter, Func<int, IData>, int> store;
        public readonly Action<BinaryReader, Action<int, IData>, int> load;

        public readonly Type Type;
        public readonly IIndexerPersist[] Persists;
        public readonly Func<Type, MemberInfo, int> MembersOrder;

        public DataIndexerPersist(Type type, IIndexerPersist[] persists, Func<Type, MemberInfo, int> membersOrder = null)
        {
            Type = type;
            Persists = persists;
            MembersOrder = membersOrder;

            store = CreateStoreMethod().Compile();
            load = CreateLoadMethod().Compile();
        }

        public DataIndexerPersist(Type T, Func<Type, MemberInfo, int> membersOrder = null)
            : this(T, IndexerPersistHelper.GetDefaultPersists(T, membersOrder), membersOrder)
        {
        }

        public Expression<Action<BinaryWriter, Func<int, IData>, int>> CreateStoreMethod()
        {
            var writer = Expression.Parameter(typeof(BinaryWriter), "writer");
            var values = Expression.Parameter(typeof(Func<int, IData>), "values");
            var count = Expression.Parameter(typeof(int), "count");

            var idx = Expression.Variable(typeof(int), "idx");
            var callValues = Expression.Convert(Expression.Call(values, values.Type.GetMethod("Invoke"), idx), typeof(Data<>).MakeGenericType(Type)).Value();

            var body = IndexerPersistHelper.CreateStoreBody(Type, Persists, writer, callValues, idx, count, MembersOrder);
            var lambda = Expression.Lambda<Action<BinaryWriter, Func<int, IData>, int>>(body, new ParameterExpression[] { writer, values, count });

            return lambda;
        }

        public Expression<Action<BinaryReader, Action<int, IData>, int>> CreateLoadMethod()
        {
            var reader = Expression.Parameter(typeof(BinaryReader), "reader");
            var values = Expression.Parameter(typeof(Action<int, IData>), "func");
            var count = Expression.Parameter(typeof(int), "count");

            var array = Expression.Variable(typeof(Data<>).MakeGenericType(Type).MakeArrayType());

            var body = DataType.IsPrimitiveType(Type) ?
                    IndexerPersistHelper.SingleSlotCreateLoadBody(Type, true, values, reader, count, Persists) :
                    Expression.Block(new ParameterExpression[] { array },
                        Expression.Assign(array, Expression.New(array.Type.GetConstructor(new Type[] { typeof(int) }), count)),
                        array.For(i =>
                        {
                            return Expression.Block(Expression.Assign(Expression.ArrayAccess(array, i), Expression.New(typeof(Data<>).MakeGenericType(Type).GetConstructor(new Type[] { }))),
                                  Expression.Assign(Expression.ArrayAccess(array, i).Value(), Expression.New(Type.GetConstructor(new Type[] { }))),
                                    Expression.Call(values, values.Type.GetMethod("Invoke"), i, Expression.ArrayAccess(array, i)));
                        }, Expression.Label(), count),
                        IndexerPersistHelper.CreateLoadBody(Type, true, reader, array, count, MembersOrder, Persists)
                    );

            return Expression.Lambda<Action<BinaryReader, Action<int, IData>, int>>(body, new ParameterExpression[] { reader, values, count });
        }

        public void Store(BinaryWriter writer, Func<int, IData> values, int count)
        {
            store(writer, values, count);
        }

        public void Load(BinaryReader reader, Action<int, IData> values, int count)
        {
            load(reader, values, count);
        }

    }
}
